/*
 * Copyright (C) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { RectangleCanvas } from './RectangleCanvas';

export class Rectangle {
  width: number;
  height: number;
  label: string;
  value: number;
  originPoint: Array<number>;
  data: Array<any>;
  fitData: Array<number>;
  bgColor: string;
  currentBgColor: string;
  areas: Array<Rectangle>;
  areasAsRoot: Array<Rectangle> | undefined;
  parentRect: Rectangle | undefined | null;
  direct: Array<number>;
  moveTo: Array<number>;
  newArea: number;
  lastSide: number;
  longSide: number;
  usedColor: number = 0;
  static RectangleLegendContainer: HTMLElement;
  static SingleCharacterLength = 7.5;
  // 颜色列表
  static colors = [
    '#8bc34a',
    '#3f51b5',
    '#03a9f4',
    '#00bcd4',
    '#cddc39',
    '#FF8C00',
    '#795548',
    '#9e9e9e',
    '#FFAAAA',
    '#2CDD00',
    '#EE11EE',
    '#FCCFFC',
    '#20c997',
    '#17a2b8',
    '#f44336',
    '#009688',
    '#9c27b0',
    '#ff5722',
    '#ffeb3b',
    '#DA70D6',
    '#607d8b',
    '#ffc107',
    '#4caf50',
    '#673ab7',
    '#58934A',
    '#257EB9',
    '#CBA0D6',
  ];

  constructor(
    width: number,
    height: number,
    data: Array<any>,
    label: string,
    value: number,
    bgColor: string,
    parentRect: Rectangle | undefined | null
  ) {
    this.width = width;
    this.height = height;
    // 标签
    this.label = label;
    // 值
    this.value = value;
    // 原点
    this.originPoint = [];
    // 数据
    this.data = [];
    // 根据面积调整过后的数据
    this.fitData = [];
    // 背景色
    this.bgColor = bgColor ? bgColor : 'rgba(200, 0, 0, 0.4)';
    // 当前绘制颜色
    this.currentBgColor = this.bgColor;
    // 绘制区域（子矩形）
    this.areas = [];
    // 父矩形
    this.parentRect = parentRect;
    // 下一组矩形绘制的“原点”
    this.moveTo = [0, 0];
    // 完成布置的矩形面积（临时量）
    this.newArea = 0;
    // 上一条没着摆放的边（初始是短边）——临时量
    this.lastSide = Math.min(this.width, this.height);
    // “长边”摆放边的邻边——临时量
    this.longSide = Math.max(this.width, this.height);
    // 行方向：[1,0]是x方向，[0,1]是y方向
    if (this.width > this.height) {
      this.direct = [1, 0];
    } else {
      this.direct = [0, 1];
    }
    if (data.length > 0) this.setData(data);
  }

  static RectangleTreemap(
    data: Array<any>,
    width: number,
    height: number,
    ox: number,
    oy: number,
    label: string,
    value: number,
    bgColor: string,
    parentRect: Rectangle | undefined | null
  ) {
    this.sortData(data);
    let BigRect = new Rectangle(width, height, data, label, value, bgColor, parentRect);
    BigRect.originPoint = BigRect.moveTo = [ox, oy];
    BigRect.calculateRectangularArea(BigRect.fitData, [], BigRect.getRectangleLayoutWidth());
    return BigRect;
  }

  private calculateRectangularArea(children: Array<number>, row: Array<number>, width: number) {
    if (children.length == 0) {
      this.layoutRow(row);
      return;
    }
    let first = children[0];
    let old_worst = this.worst(row, width);
    let new_worst = this.worst(row.concat(first), width);
    if (row.length == 0 || old_worst >= new_worst) {
      // 添加使得纵横比减小，向现有行添加c
      this.calculateRectangularArea(children.slice(1, children.length), row.concat(first), width);
    } else {
      // 添加使得纵横比增大，当前行结束填充，添加新行
      this.layoutRow(row);
      this.calculateRectangularArea(children, [], this.getRectangleLayoutWidth());
    }
  }

  /**
   * 计算最大纵横比
   * row: 包含长方形面积的列表
   * width: 摆放长方形的边的长
   */
  private worst(row: Array<number>, width: number) {
    let [...r] = row; // 拷贝，不对原数组排序
    if (r.length == 0) {
      return NaN;
    } else {
      // 从小到大排序
      r.sort();
      let max = r[r.length - 1];
      let min = r[0];
      let total = this.sumArr(r);
      return Math.max((width * width * max) / (total * total), (total * total) / (width * width * min));
    }
  }

  private layoutRow(row: Array<number>) {
    // 将布置完成的行放入矩形中，并添加绘制区域
    this.newArea = this.sumArr(row);
    // 存储绘制区域
    this.reverseDirect();
    if (this.direct[1] == 1) {
      // 沿y轴放置，w固定，h变化，x坐标不变，y坐标递增
      let width = this.newArea / this.lastSide;
      let y = this.moveTo[1];
      for (let index = 0; index < row.length; index++) {
        let height = row[index] / width;
        let x = this.moveTo[0];
        let data = this.data[this.areas.length];
        if (data.data) {
          let color = Rectangle.colors[this.usedColor++];
          if (data.data[0].label === 'no data') {
            color = '#c0c0c0';
          }
          this.areas.push(Rectangle.RectangleTreemap(data.data, width, height, x, y, data.label, data.value, color, this));
        } else {
          let rect = new Rectangle(width, height, [], data.label, data.value, this.bgColor, this);
          rect.moveTo = rect.originPoint = [x, y];
          this.areas.push(rect);
        }
        y += height; // 沿y轴前进
      }
      this.moveTo = [this.moveTo[0] + width, this.moveTo[1]];
    } else {
      // 沿x轴放置，h固定，w变化，y坐标不变，x坐标递增
      let height = this.newArea / this.lastSide;
      let x = this.moveTo[0];
      for (let index = 0; index < row.length; index++) {
        let width = row[index] / height;
        let y = this.moveTo[1];
        let data = this.data[this.areas.length];
        if (data.data) {
          this.areas.push(
            Rectangle.RectangleTreemap(
              data.data,
              width,
              height,
              x,
              y,
              data.label,
              data.value,
              Rectangle.colors[this.usedColor++],
              this
            )
          );
        } else {
          let rect = new Rectangle(width, height, [], data.label, data.value, this.bgColor, this);
          rect.moveTo = rect.originPoint = [x, y];
          this.areas.push(rect);
        }
        // 沿x轴前进w
        x += width;
      }
      this.moveTo = [this.moveTo[0], this.moveTo[1] + height];
    }
  }

  private reverseDirect() {
    this.direct.reverse();
  }

  private getRectangleLayoutWidth() {
    //获得用来布置的子矩形的短边
    if (this.newArea == 0) {
      // 初始状态
      return this.lastSide;
    }
    let result = this.longSide - this.newArea / this.lastSide;
    this.longSide = this.lastSide;
    this.lastSide = result;
    return result;
  }

  // 计算列表和
  private sumArr(arr: Array<number>) {
    return arr.reduce(function (prev, cur) {
      return prev + cur;
    }, 0);
  }

  // 对数据从大到小排序
  private static sortData(data: Array<any>) {
    data
      .sort(function (a, b) {
        return parseInt(a.value) - parseInt(b.value);
      })
      .reverse();
  }

  private setData(data: Array<any>) {
    this.data = data;
    let piece = (this.width! * this.height!) / this.sumAreasValue(this.data);
    let array = new Array();
    this.data.forEach(function (d, idx) {
      array.push(d.value * piece);
    });
    // 提取value，构成列表
    this.fitData = array;
  }

  private sumAreasValue(Ls: Array<any>) {
    let sum = 0;
    Ls.forEach((item, idx) => {
      if (item.data) {
        sum += this.sumAreasValue(item.data);
      } else {
        sum += parseInt(item.value);
      }
    });
    return sum;
  }

  private drawAreasAsRoot(canvas: RectangleCanvas, color?: string) {
    for (let index = 0; index < this.areasAsRoot!.length; index++) {
      let area = this.areasAsRoot![index];
      if (area.data.length == 0) {
        // 原子矩形
        canvas.areas.push(area);
        area.currentBgColor = color ? color : area.bgColor;
        area.draw(canvas);
      } else area.drawChildAreas(canvas, color ? color : area.bgColor);
    }
  }

  inRectangleRegion(context: any, mouseX: number, mouseY: number) {
    context.beginPath();
    context.rect(this.originPoint[0], this.originPoint[1], this.width, this.height);
    let result = context.isPointInPath(mouseX, mouseY);
    context.closePath();
    return result;
  }

  draw(canvas: RectangleCanvas) {
    let context = canvas.context;
    context.clearRect(this.originPoint[0], this.originPoint[1], this.width, this.height);
    context.fillStyle = this.currentBgColor;
    context.fillRect(this.originPoint[0], this.originPoint[1], this.width, this.height);
    context.strokeStyle = canvas.strokeColor;
    context.strokeRect(this.originPoint[0], this.originPoint[1], this.width, this.height);
    context.fillStyle = canvas.textColor;
    if (this.label.length * Rectangle.SingleCharacterLength < this.width) {
      //文字居中
      let strX = this.width / 2 - (this.label.length * Rectangle.SingleCharacterLength) / 2;
      context.fillText(this.label, this.originPoint[0] + strX, this.originPoint[1] + this.height / 2);
    } else {
      let str = this.handleTextExceedingRectangle();
      context.fillText(str, this.originPoint[0] + this.width / 2 - 10, this.originPoint[1] + this.height / 2);
    }
  }

  scrim(canvas: any) {
    let context = canvas.context;
    context.clearRect(this.originPoint[0], this.originPoint[1], this.width, this.height);
    context.fillStyle = this.currentBgColor;
    context.fillRect(this.originPoint[0], this.originPoint[1], this.width, this.height);
    // 遮罩
    context.fillStyle = 'rgba(0,0,0,0.2)';
    context.fillRect(this.originPoint[0], this.originPoint[1], this.width, this.height);
    context.strokeStyle = canvas.strokeColor;
    context.strokeRect(this.originPoint[0], this.originPoint[1], this.width, this.height);
    context.fillStyle = canvas.textColor;
    if (this.label.length * Rectangle.SingleCharacterLength < this.width) {
      let strX = this.width / 2 - (this.label.length * Rectangle.SingleCharacterLength) / 2;
      context.fillText(this.label, this.originPoint[0] + strX, this.originPoint[1] + this.height / 2);
    } else {
      let str = this.handleTextExceedingRectangle();
      context.fillText(str, this.originPoint[0] + this.width / 2 - 10, this.originPoint[1] + this.height / 2);
    }
  }

  //矩形框文字超出范围处理
  handleTextExceedingRectangle(): string {
    let str = '';
    if (this.label.length * Rectangle.SingleCharacterLength > this.width / 2) {
      let realWidth = Math.floor(this.width / 2);
      let num = Math.floor(realWidth / Rectangle.SingleCharacterLength) - 2;
      str = this.label.substring(0, num) + '...';
    } else {
      str = this.label;
    }
    //高度小于20文字显示不下
    if (this.height < 20) {
      str = '...';
    }
    return str;
  }

  // 绘制子矩形区域
  public drawChildAreas(canvas: RectangleCanvas, color?: string) {
    if (this.areas.length == 0) {
      // 原子矩形
      canvas.areas.push(this);
      this.currentBgColor = color ? color : this.bgColor;
      this.draw(canvas);
    } else {
      for (let index = 0; index < this.areas.length; index++) {
        let area = this.areas[index];
        area.drawChildAreas(canvas, color ? color : area.bgColor);
      }
    }
  }

  public drawLegendLabels() {
    Rectangle.RectangleLegendContainer.innerHTML = '';
    let allAtoms = true;
    for (let index = 0; index < this.areas.length; index++) {
      if (this.areas[index].data.length != 0) {
        allAtoms = false;
        break;
      }
    }
    if (allAtoms && this.areas.length != 0) {
      let legend = document.createElement('label');
      legend.classList.add('legend');
      legend.innerHTML = this.label + '\n' + this.value;
      legend.style.backgroundColor = this.bgColor;
      Rectangle.RectangleLegendContainer.appendChild(legend);
      return;
    }
    let hasAtom = false;
    for (let index = 0; index < this.areasAsRoot!.length; index++) {
      let area = this.areasAsRoot![index];
      if (area.data.length == 0 && this.label) {
        hasAtom = true;
      } else {
        let legend = document.createElement('label');
        legend.classList.add('legend');
        legend.innerHTML = area.label + '\n' + area.value;
        legend.style.backgroundColor = area.bgColor;
        Rectangle.RectangleLegendContainer.appendChild(legend);
      }
    }
    if (hasAtom) {
      let legend = document.createElement('label');
      legend.classList.add('legend');
      legend.innerHTML = this.label + '&nbsp;' + this.value;
      legend.style.backgroundColor = this.bgColor;
      Rectangle.RectangleLegendContainer.appendChild(legend);
    }
  }

  public changeToRoot(canvas: RectangleCanvas) {
    // 转变为为根时的布局动画
    let that = this;
    if (!this.areasAsRoot) {
      // 还没有为根时的布局模样
      if (this.data.length == 0) {
        // 原子矩形
        this.areasAsRoot = Rectangle.RectangleTreemap(
          [
            {
              label: this.label,
              value: this.value,
            },
          ],
          canvas.width,
          canvas.height,
          0,
          0,
          this.label,
          this.value,
          this.bgColor,
          null
        ).areas;
      } else {
        this.areasAsRoot = Rectangle.RectangleTreemap(
          this.data,
          canvas.width,
          canvas.height,
          0,
          0,
          this.label,
          this.value,
          this.bgColor,
          null
        ).areas;
        this.areasAsRoot.forEach((asr, i) => {
          this.areas[i].bgColor = asr.bgColor;
          this.areas[i].bgColor = asr.currentBgColor;
        });
      }
    }
    if (this.data.length == 0) {
      let rect = new DrawRect(
        canvas,
        this.originPoint[0],
        this.originPoint[1],
        this.width,
        this.height,
        this.areasAsRoot[0].originPoint[0],
        this.areasAsRoot[0].originPoint[1],
        this.areasAsRoot[0].width,
        this.areasAsRoot[0].height,
        this.bgColor,
        this.value
      );
      let alpha = 1;
      let newInterval = setInterval(function () {
        canvas.clearAll();
        alpha -= 0.04;
        canvas.context.globalAlpha = alpha;
        that.parentRect!.drawAreasAsRoot(canvas);
        canvas.context.globalAlpha = 1;
        that.drawChildAreas(canvas);
        canvas.areas = [];
        if (alpha < 0) {
          clearInterval(newInterval);
          let end = false;
          let interval = setInterval(function () {
            end = true;
            canvas.clearAll();
            rect.draw();
            if (!rect.end) end = false;
            if (end) {
              // 动画结束
              clearInterval(interval);
              canvas.clearAll();
              that.drawAreasAsRoot(canvas);
              that.drawLegendLabels();
            }
          }, 16);
        }
      }, 32);
    } else {
      let rectArr = new Array();
      this.areas.forEach((area, idx) => {
        let item = this.areasAsRoot![idx];
        let rect = new DrawRect(
          canvas,
          area.originPoint[0],
          area.originPoint[1],
          area.width,
          area.height,
          item.originPoint[0],
          item.originPoint[1],
          item.width,
          item.height,
          area.bgColor,
          area.value
        );
        rectArr.push(rect);
      });

      let alpha = 1;
      let newInterval = setInterval(function () {
        canvas.clearAll();
        alpha -= 0.04;
        canvas.context.globalAlpha = alpha;
        that.parentRect!.drawAreasAsRoot(canvas);
        canvas.context.globalAlpha = 1;
        that.drawChildAreas(canvas);
        canvas.areas = [];
        if (alpha < 0) {
          clearInterval(newInterval);
          let end = false;
          let interval = setInterval(function () {
            end = true;
            canvas.clearAll();
            rectArr.forEach(function (dr, i) {
              if (!dr.end) end = false;
              dr.draw();
            });
            if (end) {
              // 动画结束
              clearInterval(interval);
              canvas.clearAll();
              that.drawAreasAsRoot(canvas);
              that.drawLegendLabels();
            }
          }, 16);
        }
      }, 32);
    }
  }

  public changeFromRoot(canvas: RectangleCanvas) {
    // 返回原布局动画
    let that = this;
    if (this.areas.length != 0) {
      let reacArr = new Array();
      this.areas.forEach((area, idx) => {
        let item = this.areasAsRoot![idx];
        let rect = new DrawRect(
          canvas,
          item.originPoint[0],
          item.originPoint[1],
          item.width,
          item.height,
          area.originPoint[0],
          area.originPoint[1],
          area.width,
          area.height,
          area.bgColor,
          area.value
        );
        reacArr.push(rect);
      });
      let end = false;
      let interval = setInterval(function () {
        end = true;
        canvas.clearAll();
        reacArr.forEach(function (dr, i) {
          if (!dr.end) end = false;
          dr.draw();
        });
        if (end) {
          // 动画结束
          clearInterval(interval);
          that.drawChildAreas(canvas);
          setTimeout(function () {
            let alpha = 0;
            let newInterval = setInterval(function () {
              canvas.clearAll();
              alpha += 0.04;
              canvas.context.globalAlpha = alpha;
              that.parentRect!.drawAreasAsRoot(canvas);
              canvas.context.globalAlpha = 1;
              that.drawChildAreas(canvas);
              canvas.areas = [];
              if (alpha > 1.01) {
                clearInterval(newInterval);
                if (!that.parentRect!.parentRect) that.parentRect!.drawAreasAsRoot(canvas);
              }
            }, 32);
            that.parentRect!.drawLegendLabels();
          }, 100);
        }
      }, 16);
    } else {
      let rect = new DrawRect(
        canvas,
        this.areasAsRoot![0].originPoint[0],
        this.areasAsRoot![0].originPoint[1],
        this.areasAsRoot![0].width,
        this.areasAsRoot![0].height,
        this.originPoint[0],
        this.originPoint[1],
        this.width,
        this.height,
        this.bgColor,
        this.value
      );
      let end = false;
      let interval = setInterval(function () {
        end = true;
        canvas.clearAll();
        rect.draw();
        if (!rect.end) end = false;
        if (end) {
          // 动画结束
          clearInterval(interval);
          setTimeout(function () {
            let alpha = 0;
            let newInterval = setInterval(function () {
              canvas.clearAll();
              alpha += 0.04;
              canvas.context.globalAlpha = alpha;
              that.parentRect!.drawAreasAsRoot(canvas);
              canvas.context.globalAlpha = 1;
              that.drawChildAreas(canvas);
              //canvas.areas = [];
              if (alpha > 1.01) clearInterval(newInterval);
            }, 32);
            that.parentRect!.drawLegendLabels();
          }, 100);
        }
      }, 16);
    }
  }
}

export class DrawRect {
  bgColor: string;
  start_X: number;
  start_Y: number;
  start_width: number;
  start_height: number;
  end_X: number;
  end_Y: number;
  end_width: number;
  end_height: number;
  time: number;
  end: boolean;
  value: number;
  canvas: RectangleCanvas;

  constructor(
    canvas: RectangleCanvas,
    start_X: number,
    start_Y: number,
    start_width: number,
    start_height: number,
    end_X: number,
    end_Y: number,
    end_width: number,
    end_height: number,
    bgColor: string,
    value: number
  ) {
    this.bgColor = bgColor;
    this.start_X = start_X;
    this.start_Y = start_Y;
    this.start_width = start_width;
    this.start_height = start_height;
    this.end_X = end_X;
    this.end_Y = end_Y;
    this.end_width = end_width;
    this.end_height = end_height;
    this.time = 0;
    this.end = false;
    this.value = value;
    this.canvas = canvas;
  }

  public draw() {
    let context = this.canvas.context;
    if (!this.end) {
      let x = this.start_X * (1 - this.time) + this.end_X * this.time;
      let y = this.start_Y * (1 - this.time) + this.end_Y * this.time;
      let width = this.start_width * (1 - this.time) + this.end_width * this.time;
      let height = this.start_height * (1 - this.time) + this.end_height * this.time;
      context.fillStyle = this.bgColor;
      context.fillRect(x, y, width, height);
      context.fillStyle = this.canvas.textColor;
      context.fillText(this.value + '', x + 2, y + 12);
      this.time += 0.04;
      if (this.time > 1.01) {
        this.end = true;
      }
    }
  }
}
